package cn.dkdoo.utils.templateCommon.mongo;

import cn.dkdoo.utils.common.PageDataDTO;
import cn.dkdoo.utils.common.Pager;
import cn.dkdoo.utils.templateCommon.DkdTemplate;
import cn.dkdoo.utils.templateCommon.IDkdRepository;
import com.mongodb.BasicDBObject;
import com.mongodb.BulkWriteOperation;
import com.mongodb.BulkWriteResult;
import org.apache.commons.collections.CollectionUtils;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author nicky
 * @date 2021/7/5 上午11:10
 */
public abstract class DkdMongoTemplate<T> extends DkdTemplate<T> implements IDkdRepository<T>, InitializingBean {
    private MongoOperations template;

    public DkdMongoTemplate() {
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.template = initTemplate();
        super.afterPropertiesSet();
    }

    public abstract MongoOperations initTemplate();

    @Override
    public String initCollectionName() {
        return this.template.getCollectionName(super.beanClass);
    }

    @Override
    public T insert(T t) {
        this.template.insert(t, super.collectionName);
        return t;
    }

    @Override
    public List<T> insert(List<T> ts) {
        this.template.insert(ts, super.collectionName);
        return ts;
    }

    @Override
    public T save(T t) {
        this.template.save(t, super.collectionName);
        return t;
    }

    @Override
    public List<T> save(List<T> ts) {
        boolean allNew = true;
        for (T item : ts) {
            if (allNew && findIdVal(item) != null) {
                allNew = false;
                break;
            }
        }

        if (allNew) {
            this.insert(ts);
        } else {
            for (T item : ts) {
                this.save(item);
            }
        }
        return ts;
    }

    public String findIdVal(T t) {
        try {
            return String.valueOf(idField.get(t));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public T findById(String id, String... fields) {
        DkdCriteria dkdCriteria = new DkdCriteria();
        dkdCriteria.and("_id").is(id);
        Query query = Query.query(dkdCriteria);
        DkdMongoUtils.fiterFields(query, fields);
        return this.template.findOne(query, super.beanClass);
    }

    @Override
    public List<T> findByIds(Collection<String> ids, String... fields) {
        DkdCriteria dkdCriteria = new DkdCriteria();
        dkdCriteria.and("_id").in(ids);
        Query query = Query.query(dkdCriteria);
        DkdMongoUtils.fiterFields(query, fields);
        return this.template.find(query, super.beanClass);
    }

    @Override
    public Map<String, T> findToMap(Collection<String> ids, String... fields) {
        List<T> resList = this.findByIds(ids, fields);
        if (CollectionUtils.isEmpty(resList)) {
            return null;
        }
        return resList.stream().collect(Collectors.toMap(
                item -> findIdVal(item),
                Function.identity(),
                (item1, item2) -> item1
        ));
    }

    //@Override
    public Map<String, T> findToMap(Query query, String... fields) {
        DkdMongoUtils.fiterFields(query, fields);
        List<T> resList = this.template.find(query, super.beanClass);
        if (CollectionUtils.isEmpty(resList)) {
            return null;
        }
        return resList.stream().collect(Collectors.toMap(
                item -> findIdVal(item),
                Function.identity(),
                (item1, item2) -> item1
        ));
    }

    //@Override
    public Map<String, T> findToMap(Criteria criteria, String... fields) {
        Query query = Query.query(criteria);
        DkdMongoUtils.fiterFields(query, fields);
        return findToMap(query, fields);
    }

    @Override
    public Map<String, T> findToMapGroupSelf(Collection<String> ids, Function<? super T, ? extends String> groupKeyFunction, String... fields) {
        List<T> resList = this.findByIds(ids, fields);
        if (CollectionUtils.isEmpty(resList)) {
            return null;
        }
        return resList.stream().collect(Collectors.toMap(
                groupKeyFunction,
                Function.identity(),
                (item1, item2) -> item1
        ));
    }

    @Override
    public int removeById(String id) {
        DkdCriteria dkdCriteria = new DkdCriteria();
        dkdCriteria.and("_id").is(id);
        Query query = Query.query(dkdCriteria);
        return this.template.remove(query, super.beanClass).getN();
    }

    @Override
    public int removeByIds(Collection<String> ids) {
        DkdCriteria dkdCriteria = new DkdCriteria();
        dkdCriteria.and("_id").in(ids);
        Query query = Query.query(dkdCriteria);
        return this.template.remove(query, super.beanClass).getN();
    }

    @Override
    public PageDataDTO<T> doPage(Integer pageSize, Integer pageNo, Query query, Sort... sorts) {
        long total = this.template.count(query, super.beanClass);
        Pager pager = new Pager(pageSize, pageNo, total);
        query.skip((pager.getPageNo() - 1) * pager.getPageSize())
                .limit(pager.getPageSize());
        if (sorts != null && sorts.length > 0) {
            for (Sort sort : sorts) {
                query.with(sort);
            }
        }
        List<T> resList = this.template.find(query, super.beanClass);
        PageDataDTO<T> res = new PageDataDTO<>();
        res.setData(resList);
        res.setPage(pager);
        return res;
    }

    public BulkWriteOperation findBulkWriteOperation() {
        return template.getCollection(super.collectionName).initializeOrderedBulkOperation();
    }

    public BulkWriteResult bulk(BulkWriteFunction bulkWriteFunction) {
        BulkWriteOperation bulkWriteOperation = findBulkWriteOperation();
        bulkWriteFunction.buildBulk(bulkWriteOperation);
        return bulkWriteOperation.execute();
    }

    public <R> List<R> aggregate(Aggregation aggregation, Class<R> outputClass) {
        AggregationResults<R> aggregationResults = template.aggregate(aggregation, super.beanClass, outputClass);
        return aggregationResults.getMappedResults();
    }

    public List<T> aggregate(Aggregation aggregation) {
        AggregationResults<T> aggregationResults = template.aggregate(aggregation, super.beanClass, super.beanClass);
        return aggregationResults.getMappedResults();
    }

}
